

/* test_mutex_when_killed_1():
	This test checks that when a thread is killed - the mutexes are handled
	correctly.

	The following is done.
	* We use the following global mutexes: A,B.
	* Creating additional threads: 1,2,3.
	* Thread 1 locks mutex A.
	* Thread 2 locks mutex B.
	* Thread 2 locks mutex A (and gets blocked).
	* Thread 1 locks mutex B (and gets blocked).
	* Thread 3 locks mutex B (and gets blocked).
	* Thread 0 cancels thread 1. So then:
		* Thread 2 gets hold on mutex A, return to run, and exits.
		* Thread 3 gets hold on mutex B, return to run, and exits.
	* Theard 0 destroys A,B.
	* Thread 0 does join to threads 1,2,3.

*/


#include <assert.h>
#include <stdio.h>
#include <stdbool.h>

#include "test_general.h"
#include "user_threads.h"
#include "user_mutex.h"


bool test_mutex_when_killed_1();


#define THREADS_NUM 4

uthread_mutex_t mutex_a, mutex_b;


static int thread1_start_routine(void * param) {
	TEST_ASSERT (uthread_self() == 1);

	int tmp;
	tmp = uthread_mutex_lock(mutex_a);
	TEST_ASSERT (MUTEX_SUCCESS == tmp);

	some_long_operation(0.1); // No thread 2 locks mutex_b.

	tmp = uthread_mutex_try_lock(mutex_b);
	thread_ok[1] = true;

	TEST_ASSERT (MUTEX_LOCKED == tmp);
	tmp = uthread_mutex_lock(mutex_b);
		/* we are now blocked until thread 2 is canceled */
	TEST_ASSERT (MUTEX_SUCCESS == tmp);

	return 0;
}


static int thread2_start_routine(void * param) {
	TEST_ASSERT (uthread_self() == 2);

	int tmp;
	tmp = uthread_mutex_lock(mutex_b);
	TEST_ASSERT (MUTEX_SUCCESS == tmp);

	some_long_operation(0.1); // No thread 1 locks mutex_a.

	tmp = uthread_mutex_try_lock(mutex_a);
	TEST_ASSERT (MUTEX_LOCKED == tmp);
	tmp = uthread_mutex_lock(mutex_a);
		/* we are now blocked until thread 1 is canceled */
	TEST_ASSERT (MUTEX_SUCCESS == tmp);

	some_long_operation(0.01);
	TEST_ASSERT(last_running_times[1] < last_running_times[2]-0.3);

	thread_ok[2] = true;
	return 0;
}

static int thread3_start_routine(void * param) {
	TEST_ASSERT (uthread_self() == 3);

	some_long_operation(0.1); // No thread 2 locks mutex_b.

	int tmp = uthread_mutex_try_lock(mutex_b);
	TEST_ASSERT (MUTEX_LOCKED == tmp);
	tmp = uthread_mutex_lock(mutex_b);
		/* we are now blocked until thread 2 exits */
	TEST_ASSERT (MUTEX_SUCCESS == tmp);

	some_long_operation(0.01);
	TEST_ASSERT(last_running_times[1] < last_running_times[2]-0.3);
	TEST_ASSERT(last_running_times[2] > last_running_times[3]-0.5);

	thread_ok[3] = true;
	return 0;
}


bool test_mutex_when_killed_1()
{
	init_individual_test();

	int tmp;

	if (initilize_uthread_in_each_test) {
		tmp = uthread_init();
		TEST_ASSERT(tmp == UTHREAD_SUCCESS);
	}

	tmp = uthread_mutex_init(&mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);
	tmp = uthread_mutex_init(&mutex_b);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	thread_id threads[THREADS_NUM];

	threads[0] = uthread_self();
	TEST_ASSERT(threads[0] == 0);

	int FiftyThree = 53;

	threads[1] = uthread_create(thread1_start_routine, &FiftyThree);
	TEST_ASSERT(threads[1] == 1);

	threads[2] = uthread_create(thread2_start_routine, &FiftyThree);
	TEST_ASSERT(threads[2] == 2);

	threads[3] = uthread_create(thread3_start_routine, &FiftyThree);
	TEST_ASSERT(threads[3] == 3);


	some_long_operation(0.4);
	tmp = uthread_cancel(threads[1]);
	TEST_ASSERT(UTHREAD_SUCCESS == tmp)

	some_long_operation(0.1);

	tmp = uthread_mutex_destroy(mutex_a);
	TEST_ASSERT(MUTEX_SUCCESS == tmp);
	tmp = uthread_mutex_destroy(mutex_b);
	TEST_ASSERT(MUTEX_SUCCESS == tmp);

	tmp = uthread_join(threads[1],NULL);
	TEST_ASSERT(UTHREAD_SUCCESS == tmp);
	tmp = uthread_join(threads[2],NULL);
	TEST_ASSERT(UTHREAD_SUCCESS == tmp);
	tmp = uthread_join(threads[3],NULL);
	TEST_ASSERT(UTHREAD_SUCCESS == tmp);


	thread_ok[0] = true;
	TEST_ASSERT(are_all_threads_ok(THREADS_NUM));

	return true;
}
